LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

Bonbon

Go farther and see the brighter light

7dof_robot

2022/2/20

这学期机器人学的大作业是做一个七自由度机械臂,

其中六个转动关节,一个伸缩关节

前三个转动关节+一个伸缩关节+末尾三个转动关节

这里记录一下我耗时最久的解析解

首先其实七自由度是有冗余的,我们求解只需要求解六个关节,

我是求解六个旋转关节,然后伸缩关节用于后续规划。

这种解析解的方法是适用于常规机械臂,即前面的关节确定腕部位置,后三个关节确定姿态。


求解θ1\theta_1 θ2\theta_2 θ3\theta_3

将机械臂简化成下图,用于分析解析解。

我们已知机械臂末端的位姿0T7^{0}T_7

0T7=0T11T22T33T44T55T66T7=[0R70P701]^{0}T_7 = ^0T_1\, ^1T_2\, ^2T_3\, ^3T_4\, ^4T_5\, ^5T_6\, ^6T_7 = \begin{bmatrix} ^{0}R_7 & ^{0}P_7 \\ 0 & 1\\ \end{bmatrix}

首先我们根据末端坐标系将P07P_{07}转换到P06P_{06},记aa0R7.a{^{0}}R_7.a,则

0P6=0P7aL ^{0}P_6 = {^{0}}P_7 - aL

而坐标系5与坐标系6原点重合,所以有

0P5=0P6 ^{0}P_5 = {^{0}}P_6

现在已知0P5^{0}P_5,而0P5^{0}P_5仅是关于θ1\theta_1θ2\theta_2θ3\theta_3的函数。三个未知数,三个方程,可解。将其不断展开如下

0P5=0T11T22T33T44P5\begin{matrix} ^{0}P_5 = {^{0}}T_1 \, ^{1}T_2 \, ^{2}T_3 \, ^{3}T_4 \, ^{4}P_5 \\ \end{matrix}

=0T11T22T33T4[00d51]= {^{0}}T_1 \, ^{1}T_2 \, ^{2}T_3 \, ^{3}T_4 \, \begin{bmatrix} 0 \\ 0 \\ d_5 \\ 1 \\ \end{bmatrix}

=0T11T22T3[00d4+d51]={^{0}}T_1 \, ^{1}T_2 \, ^{2}T_3 \, \begin{bmatrix} 0 \\ 0 \\ d_4+d_5 \\ 1 \\ \end{bmatrix}

=0T11T2[(d4+d5)Sθ3(d4+d5)Cθ301]= {^{0}}T_1 \, ^{1}T_2 \, \begin{bmatrix} (d_4+d_5)S\theta_3 \\ -(d_4+d_5)C\theta_3 \\ 0 \\ 1 \\ \end{bmatrix}

=0T1[f1Cθ2f2Sθ2+a2Cθ2f1Sθ2+f2Cθ2+a2Sθ2f31] = {^{0}}T_1 \, \begin{bmatrix} f_1C\theta_2-f_2S\theta_2+a_2C\theta_2 \\ f_1S\theta_2+f_2C\theta_2+a_2S\theta_2 \\ f_3 \\ 1 \\ \end{bmatrix}

=[g1Cθ1+g3Sθ1g1Sθ1g3Cθ1g2+d11]= \begin{bmatrix} g_1C\theta_1+g_3S\theta_1 \\ g_1S\theta_1-g_3C\theta_1 \\ g_2+d_1 \\ 1 \\ \end{bmatrix}

其中

f1=(d4+d5)Sθ3g1=f1Cθ2f2Sθ2+a2Cθ2f2=(d4+d5)Cθ3g2=f1Sθ2+f2Cθ2+a2Sθ2f3=0g3=f3f_1 = (d_4+d_5)S\theta_3 \quad\qquad g_1 = f_1C\theta_2-f_2S\theta_2+a_2C\theta_2\\ f_2 = -(d_4+d_5)C\theta_3 \qquad g_2 = f_1S\theta_2+f_2C\theta_2+a_2S\theta_2\\ f_3 = 0 \qquad\qquad\qquad\qquad g_3 = f_3\\

所以

[pxaLpyaLpzaL]=[g1Cθ1+g3Sθ1g1Sθ1g3Cθ1g2+d1]\begin{bmatrix} p_x-aL \\ p_y-aL \\ p_z-aL \\ \end{bmatrix} = \begin{bmatrix} g_1C\theta_1+g_3S\theta_1 \\ g_1S\theta_1-g_3C\theta_1 \\ g_2+d_1 \\ \end{bmatrix}

通过平方和可以消掉θ1\theta_1,令

r=(pxaL)2+(pyaL)2+(pzaL)2=k1(θ2,θ3) r = ({p_x-aL})^2 + ({p_y-aL})^2 + ({p_z-aL})^2 = k_1(\theta_2,\theta_3)

pzaL=g2+d1=k2(θ2,θ3) p_z - aL = g_2+d_1 = k_2(\theta_2,\theta_3)

则我们获得了两个仅含未知数θ2\theta_2θ3\theta_3的两个方程,可解。化简可得

θ3=asin[r(d4+d5)2a22+d122d1z2a2(d4+d5)] \theta_3 =asin[\frac{r-(d_4+d_5)^2-{a_2}^2+{d_1}^2-2 d_1 z}{2 a_2 (d_4+d_5)}]

观察得g3=0g_3=0,所以

θ1=atan2(pyaL,pxaL) \theta_1 =atan2(p_y-aL,p_x-aL)

求解θ2\theta_2是求解

ACθ2+BSθ2=C AC\theta_2 + BS\theta_2 = C

其中

A=(d4+d5)Cθ3B=a2+(d4+d5)Sθ3C=zd1 A = -(d_4+d_5) C\theta_3 \\ B = a_2+(d_4+d_5) S\theta_3 \\ C = z -d_1 \\

t=tanθ22 t = tan\frac{\theta_2}{2}

cos(θ2)=1t21+t2sin(θ2)=2t1+t2 cos(\theta_2)=\frac{1-t^2}{1+t^2} \\ sin(\theta_2)=\frac{2t}{1+t^2} \\

所以可解得

t=B±B2+A2C2A+Cθ2=2atan(t)t = \frac{B \pm \sqrt{B^2+A^2-C^2}}{A+C} \\ \theta_2 = 2atan(t) \\

假设θ1,θ2,θ3\theta_1,\theta_2,\theta_3的取值范围为(02π)(0 \sim 2\pi)

θ1,θ2,θ3\theta_1,\theta_2,\theta_3各有两个解,共八组解,但是其中有四组解是错误的。

原因在于求解θ1\theta_1时使用θ1=atan2(y,x)\theta_1 =atan2(y,x),只关注yyxx的比值关系,

有四组错误的解是yyxx与正确的解正负号颠倒,

故我们要增加一个关于xx或者关于yy的条件,

在八组解里筛选出来正确的解。可以使用条件如下

pxaL=g1Cθ1+g3Sθ1pyaL=g1Sθ1g3Cθ1\begin{matrix} p_x-aL = g_1C\theta_1+g_3S\theta_1 \\ p_y-aL = g_1S\theta_1-g_3C\theta_1 \\ \end{matrix}

最终可解得正确的四组θ1,θ2,θ3\theta_1,\theta_2,\theta_3

四组解其实只对应两种姿态,同姿态的两组解θ3\theta_3相差π\pi


对于三角函数的解的特殊情况,做如下处理

如果不在工作空间的点,θ3\theta_3求解为复数,则不继续计算。

求解θ2\theta_2有可能出现单解情况,处理成两个相同的解,保证最后有四组解。


求解θ5\theta_5 θ6\theta_6 θ7\theta_7

已知θ1,θ2,θ3\theta_1,\theta_2,\theta_3,可得到0T3^{0}T_3

0T3=0T11T22T3\begin{matrix} ^{0}T_3 = {^{0}}T_1 \, ^{1}T_2 \, ^{2}T_3 \\ \end{matrix}

又已知0T7^{0}T_7,所以可得3T7^{3}T_7

3T7=3T00T7\begin{matrix} ^{3}T_7 = ^{3}T_0 \, ^{0}T_7 \\ \end{matrix}

因为

3R7=[Sθ5Sθ7+Cθ5Cθ6Cθ7Cθ7Sθ5Cθ5Cθ6Sθ7Cθ5Sθ6Cθ5Sθ7+Sθ5Cθ6Cθ7Cθ7Cθ5Sθ5Cθ6Sθ7Sθ5Sθ6Cθ7Sθ6Sθ6Sθ7Cθ6]^{3}R_7 = \begin{bmatrix} S\theta_5 S\theta_7 + C\theta_5 C\theta_6 C\theta_7 & C\theta_7 S\theta_5-C\theta_5 C\theta_6 S\theta_7 & C\theta_5 S\theta_6 \\ -C\theta_5 S\theta_7 + S\theta_5 C\theta_6 C\theta_7 & -C\theta_7 C\theta_5-S\theta_5 C\theta_6 S\theta_7 & S\theta_5 S\theta_6 \\ C\theta_7 S\theta_6 & -S\theta_6 S\theta_7 & -C\theta_6 \\ \end{bmatrix}

所以,当Sθ60S\theta_6 \neq 0时,

θ6=atan2(Sθ6,Cθ6)θ5=atan2(R23,R13)θ7=atan2(R32,R31) \begin{matrix} \theta_6 = atan2(S\theta_6, C\theta_6)\\ \theta_5 = atan2(R_{23}, R_{13})\\ \theta_7 = atan2(-R_{32}, R_{31})\\ \end{matrix}

Sθ6=0S\theta_6 = 0时,

θ6=π,θ5=0,θ7=atan2(R12,R11)θ6=0,θ5=0,θ7=atan2(R12,R11) \theta_6 = \pi , \theta_5=0 , \theta_7=atan2(R_{12}, -R_{11})\\ \theta_6 = 0 , \theta_5=0 , \theta_7=atan2(-R_{12}, R_{11})

所以最终我们可以解得四组θ1,θ2,θ3,θ5,θ6,θ7\theta_1,\theta_2,\theta_3,\theta_5,\theta_6,\theta_7


确定最后的解

因为没有实际躲避碰撞要求,规划最后解就采用关节变化最小的解。

内循环则挑选四组解中与当前关节角度变化最小的一组解

外循环则迭代伸缩关节,计算出不同伸缩关节长度对应的关节变化的最小值的解

然后最后再取所有的最小值,即为最后的解。


Matlab代码

其中全局的Link包含了机械臂的各种当前信息

解析解求解完也只需更新Link

function analytical_inverse_slove(T,dz,dx,alf,telescopic_length)
% 逆运动学求解 解析解 然后更新到目前Link的角度
% T为末端位姿
% dz dx alf为DH表参数
% telescopic_length可伸缩的长度
 			
 			
    % 获得当前关节角度 用于做解的判断
    global Link
    theta = zeros(1,7);
    for i = 2:8
        theta(i-1) = Link(i).th;
    end
 			
 	% 根据伸缩关节dh表的正负确定迭代的最小值和最大值
    if dz(4)<0
        dz_min = dz(4) - telescopic_length;
        dz_max = dz(4);
    else
        dz_min = dz(4);
        dz_max = dz(4) + telescopic_length;
    end
 			
    % 伸缩关节规划
    ths = []; % 存放关节伸缩规划过程中可用的th
    dzs = []; % 存放关节伸缩规划过程中对应的伸缩关节长度
 	for m = dz_min:2:dz_max     
 		dz(4) = m ;
 			
        link_target = T(1:3,4) + T(1:3,3) * abs(dz(7)); % 末端到最后一个关节
        r = sum(link_target.*link_target); % x^2+y^2+z^2
        x = link_target(1);
        y = link_target(2);
        z = link_target(3);
 			
 		% 求解th3
        th3_1 = asin((r-(dz(4)+dz(5))^2-dx(2)^2+dz(1)^2-2*dz(1)*z)/(2*dx(2)*(dz(4)+dz(5))));
        th3_2 = pi-th3_1;
        th3 = [th3_1,th3_2];
 			
        % 求解th1
        th1_1 =atan2(y,x);
        th1_2 = th1_1 + pi;
        th1 = [th1_1,th1_2];
 			
 		% 求解th2    
 		th_123 = zeros(4,3); % 四个解
 		index =1;
 		for j =1:length(th1)
 			for i=1:length(th3)   
                A = -(dz(4)+dz(5))*cos(th3(i));
                B = dx(2)+(dz(4)+dz(5))*sin(th3(i));
                C = z -dz(1);
                th2_1_2=solve_cos_plus_sin(A,B,C); % 两个解
 				for k=1:length(th2_1_2)
 					g1 = (dx(2)+(dz(4)+dz(5))*sin(th3(i)))*cos(th2_1_2(k))+...
 						 (dz(4)+dz(5))*cos(th3(i))*sin(th2_1_2(k));
 					if abs(cos(th1(j))* g1-x)<1e-11 && abs(sin(th1(j))* g1-y)<1e-11
 						th_123(index,:) = [th1(j),th2_1_2(k),th3(i)];
 						index = index + 1;
 					end
 				end
 			end
 		end
 			
 		if ~isreal(th_123) 
 			continue  %无解则保留之前的角度
 		end
 		
 		% 求解th5 th6 th7 			
        R06 = T(1:3,1:3);
        th_567 = ones(size(th_123,1),3);
        for i = 1:size(th_123,1)
            T01 = getT(th_123(i,1),dz(1),dx(1),alf(1));
            T12 = getT(th_123(i,2),dz(2),dx(2),alf(2));
            T23 = getT(th_123(i,3),dz(3),dx(3),alf(3));
            T03 = T01 * T12 * T23;
            R03 = T03(1:3,1:3);
            R36 = R03 \ R06;
 			[th_567(i,1), th_567(i,2), th_567(i,3)] = solve_R36(R36);    
 		end
 			
 		th = [th_123, zeros(size(th_123,1),1), th_567]; %4行7列
 			
 		errs = zeros(1,4);
 		for i = 1:size(th_123,1)
 			errs(i) = sum((th(i,:) - theta) .* (th(i,:) - theta));
 		end
 		[~,min_index]=min(errs);			
 			
 		ths = [ths;th(min_index,:)];
 		dzs = [dzs;m];
 	end
 			
 	if size(ths,1)~=0
 		errs = zeros(1,size(ths,1));
 		for i = 1:size(size(ths,1))
 			errs(i) = sum((ths(i,:) - theta) .* (ths(i,:) - theta));
 		end
 		[~,min_index] = min(errs); %关节变化最小
 			
 		dz(4) = dzs(min_index);
 		calc_DH(ths(min_index,:),dz,dx,alf); %更新到Link
 	end
 			
end

参考:https://www.bilibili.com/video/BV1v4411H7ez?p=30

阅读全文

voice_assistant

2022/2/20

制作语音助手

思路

制作语音助手其实不难,只是步骤有点繁琐,但是其实每一步都挺简单的。

步骤大致分为以为5点

1.将输入保存成语音文件(RECORD)

2.读取语音文件获得文本(ASR)

3.对文本处理输出所需要的文本(NLP)

4.将输出文本转换成语音文件(TTS)

5.将语音文件播出(PLAY)


其中第一点和第五点可以放到一起,支持录音的一般也支持播音。

其中第二点和第四点可以放到一起,支持语音识别的一般也支持语音合成。

第三点可以简单对文本处理,实现任务型的语音助手,

也可以用神经网络,也有现成的图灵机器人,实现交流型的语音助手。


RECORD和PLAY

这一方面可以用pyaudio实现,我也实际实践过

但是它封装后可设置得参数指向性不是很明显

而且效果也没有系统自带的好

所以最后选择了用python执行命令行程序

调用系统自带的arecord来RECORD,用aplay来PLAY

即方便又简洁还好用。


pyaudio

pyaudio代码可以参考源码:http://people.csail.mit.edu/hubert/pyaudio/

其中有几个地方要注意

1.录音和播音过程会出现报错信息,但是不影响录音,可以关闭信息

import sys,os

os.close(sys.stderr.fileno()) 

2.play的代码有问题

# 源码
data = wf.readframes(CHUNK)

while data != '':
    stream.write(data)
    data = wf.readframes(CHUNK)

# while的判断有问题 因为就算readframes读到最后 返回的也是b'' != '' 所以是死循环
# 应该改成 while len(data) >0:

# 或者直接改成下面这样子
while True:
    data = wf.readframes(CHUNK)
    if len(data) > 0:
        stream.write(data)
    else:
        break

3.有bug时注意查看播放设备对不对以及其他各个参数

pyaudio的文档:http://people.csail.mit.edu/hubert/pyaudio/docs/

stream类有挺多参数可以设置的


系统自带arecord

  1. 查看可录音设备
arecord -l

留意card和device后面的数字 例如card 1 device 0


  1. 录音
arecord -D plughw:1,0 -d 5 -f cd -r 16000 -c 1 -t wav test.wav

hw代表直接访问硬件,plughw代表经过采样率和格式转换插件。

-D 指定设备 hw:1,0 代表card1 device0

-d 指定录音时间

-f 指定录音格式

-r 指定采样率

-c 指定采样通道

-t 指定文件格式


  1. python调用
import os
os.system("arecord -D plughw:1,0 -d 5 -f cd -r 16000 -c 1 -t wav test.wav")

参考:https://blog.csdn.net/z2066411585/article/details/99089141

参考:https://blog.csdn.net/xiongtiancheng/article/details/80577478


系统自带aplay

  1. 查看可播放设备
aplay -l

留意card和device后面的数字 例如card 1 device 0


  1. 播放
aplay -D plughw:1,0 test.wav

  1. python调用
import os
os.system("aplay -D plughw:1,0 test.wav")

ASR和TTS

这一方面我使用 百度语音识别 直接看官方的例程和开发文档

网址:https://cloud.baidu.com/doc/SPEECH/s/Vk38lxily


首先要创建一个应用,获得 API Key和Secret Key,然后套入官方代码就可以使用了。

代码使用官方代码:https://github.com/Baidu-AIP/speech-demo

API Key和Secret Key不要手输!!直接复制粘贴!!

因为真的会分辨不了,我把I(大写i)输错成l(小写L),debug了好久!


可以先将API_KEY和SECRET_KEY带入到下面网址,然后输入到网页验证是否可用

http://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=API_KEY&client_secret=SECRET_KEY

补充一下ASR后续的处理

输出是一个很长的字符串,假设是’“xxx”:[xxx],“result”:[“你好。”]’

我们需要将其中的你好。提取出来

我使用的是正则表达式,先匹配"result":[],

然后再把输出结果切片获得我们所需的文本,

注意末尾是有标点符号的,但是我们不要将其剔除掉

因为语音合成会将标点符号也计算进去。

代码如下

#solve output
match = re.search('"result":\[(.*)]', result_str)#正则表达式匹配
if match:
    start, end = match.span()
    result = result_str[start + 11:end - 2]#切片获得所需文本
    print(result,'\n')

各个模块实现了,接下来就是中间的逻辑处理了,大致处理如下,叮咚为唤醒名称。

def record(device,time,rate,file):
    print(" * RECORD")
    cmd = "arecord -D plughw:"+\
          str(device)+\
          " -d " +\
          str(time)+\
          " -f cd -r "+\
          str(rate)+\
          " -c 1 -t wav " +\
          file
    os.system(cmd)
    print()

def play(device,file):
    print(" * PLAY")
    cmd = "aplay -D plughw:"+\
          device+\
          " " + \
          file
    os.system(cmd)
    print()

    
flag = False # 唤醒flag
cnt = 0 # 非任务语音计数
while True:
    output_text = " " # 输出的文本

    # 录音并语音识别
    record(device,record_time,rate,"./record.wav")
    text = asr("./record.wav",rate)

    if flag:
        # 这里就通过if elif 罗列所需要的任务
        if 0:
            pass
        elif 0:
            pass           
        else:
            cnt += 1
            output_text = " "

    # 计数大于一定值则结束唤醒
    if cnt >5:
        flag = False
        cnt = 0

    if "叮咚" in text:
        flag = True
        output_text = "在"

    print("输出语音:",output_text)
    if flag:
        # 语音合成并播放
        tts(output_text, "./play.wav")
        play(device,"./play.wav")

由于语音识别结果均为汉字,我们有要求要将其中的数字提取出来,也就是九转成9、十二转成12这种,

实现思路就是先将所有汉字单独转为数字,

然后变步长遍历

如果是0,就步长跨1

如果是小于10的数,就与下一位相乘,然后累加到result里,然后步长跨2

如果是大于10的数,直接累计到result里,然后步长跨1

该代码仅适用于正数,代码实现如下

def chinese_char2num(character):
    result = -1
    characters = {"零": 0, "一": 1, "二": 2, "三": 3, "四": 4,
                  "五": 5, "六": 6, "七": 7, "八": 8, "九": 9,
                  "十": 10, "百": 1e2, "千": 1e3, "万": 1e4, }
    for c in characters:
        if character == c:
            result = characters[c]

    return result


def chinese_chars2num(text):
    success = True
    result = 0

    # 将每一个汉字转为数字
    nums = []
    for t in text:
        num = chinese_char2num(t)
        nums.append(num)

    i = 0
    while True:
        if nums[i] < 0:
            success = False
            break
        if nums[i] == 0:
            i += 1

        elif nums[i] < 10:
            if i + 1 < len(nums):
                if nums[i + 1] >= 10:
                    result += nums[i] * nums[i + 1]
            else:
                result += nums[i]
            i += 2
        else:
            result += nums[i]

            i += 1
        if i > len(nums):
            break

    if not success:
        result = -1
    return result


print(chinese_chars2num('一万二千三百四十五'))

最后放一张我们小组最后实现的结果,是一个可以实时显示当前空气质量可以定时提醒,并具有语音交互功能的小电视。

阅读全文

Transformer

2022/2/20

讲一下对transformer的理解。

参考视频链接:https://www.bilibili.com/video/BV1M44y1q7oq

首先是attention注意力机制,以图像举例,图像不同位置的东西我们的注意力是不一样的。通俗点讲就是一副图像我们肯定有关注的东西,也肯定有不关注,我们对不同部分的关注度是不一样的。现在我们需要用一种算法,让计算机帮我们知道,什么位置的注意力应该高,什么样的注意力应该低。而transformer就实现了这种算法。transformer本来是运用在NLP中的,你不能单纯对一个单词进行分析,因为同一个单词可能在不同的句子里是不一样的意思,不同的句子我们放的注意力的位置也是不一样的,也就是我们常说的理解句子要联系上下文。

transformer做的就是将一个向量(或者叫token)和包括自己在内的所有向量计算关系

然后重构成一个新向量。


举个例子:

向量1 向量2 向量3 向量4
向量1 0.5 0.2 0.2 0.1

这样子向量1就重构成

这样子每一个向量都包含了全局信息,也表示了自己在全局应该得到的注意力。

至于这些权重怎么算,就需要涉及到三个辅助向量q,k,vq,k,v

qq代表的是queries,用于自己计算自己和其他向量的关系。

kk代表的是keys,用于其他向量计算其他向量和自己的关系。

vv代表的是values,用于表示自己的特征。

q,k,vq,k,v都是通过输入xx训练出来的,q=Wqx,k=Wkx,v=Wvxq=W_q*x,k=W_k*x,v=W_v*x。对应的WW就是要训练的权重。

假设xix_i对应qi,ki,kiq_i,k_i,k_i,内积越大,两个向量关系就大,内积越小,两个向量关系就小。

为了表示方便,我用rr表示关系(relation),则x1x_1xix_i的关系可以表示为r1i=q1kir_{1i}=q_1k_i

因为xx各自之间可能维度不同,为了消除维度带来的影响,所以r1ir_{1i}还要除以dimi\sqrt{dim_i}

然后r1ir_{1i}经过softmaxsoftmax归一化得到(r_n)1i(r\_n)_{1i}nn代表归一化normalized。

x1x_1重构为i=1n(r_n)1ivi\sum_{i=1}^n(r\_n)_{1i}*v_i。其余依次类推。

以上就是transformer做得内容了。transformer作为一个backbone,可以广泛地运用到其他领域。



比如在计算机视觉中的应用,即Vision Transformer(Vit)。

Vit论文:https://arxiv.org/abs/2010.11929

Vit代码:https://github.com/google-research/vision_transformer

计算机视觉中很重要一点就是感受野,transformer提高了感受野,使得网络有观察全局的能力。

首先是需要将图像转化成一个个向量,这很简单。

只需要将图像分割成一块块,然后全连接就行了。

那么输入解决了,直接套用transformer就行了吗?

不是的。因为这样子就丢失了位置信息。

因为不论你分割完怎么排列,最后每个向量算出来的结果都是一样的。

而在图像中位置信息非常重要,分割完的每一块都有其特定的位置。

所以需要进行位置编码,将位置信息融入进去。

位置编码有一维的,例如把图片按1、2、3、…排列,

位置编码有二维的,例如把图片按坐标(1,1)、(1,2)、…排列,

理论上应该是二维编码效果比一维的好,但是论文中说实验表明,两者差不多。

不太符合直观感受,因为理论上“(2,1)和(1,1)” 比 “4和1”更能表示位置信息。

另外它还增加了一个0,添加进去一起训练,用于最终预测结果,如上图左侧。


总结一下transformer和cnn差别,

transformer增加了感受野,少层就能捕获到全局信息。而cnn需要多层堆叠,才能获得全局信息。

所以transformer对整副图像的特征提取就做得很好,在很多网络中都表现很好。

但是研究表明,transformer需要的数据量很大


补充一下Multi-Head Attention

简单来讲就是不同的东西对同一个东西的注意力是不一样的,

所以我们最好就是参考很多(Multi)东西对同个东西的注意力。

不同的初始化权重WW对应不同的q,k,vq,k,v,不同的q,k,vq,k,v对应不同输出。

我们要做的就是初始化多组用多组不同的输出,拼接,然后全连接。

这样子最终的输出就包含不同的q,k,vq,k,v对应的注意力的信息了,就会更全面。

其实就跟卷积使用多个卷积核是一样的道理。

阅读全文

ASPP

2022/2/20

SPP(Spatial Pyramid Pooling)

论文链接:https://ieeexplore.ieee.org/document/7005506


让不同的输入维度有同样的维度的输出,就是SPP所实现的。

如上图

假设输入特征图是 nn x mm x cc

第一次直接对整个图池化,得到就是 11 x 11 x cc

第二次将图像分成4份,对每一份都池化,得到的就是4个11 x 11 x cc

第二次将图像分成16份,对每一份都池化,得到的就是16个11 x 11 x cc

然后将21个11 x 11 x cc拼接,就有2121 x 11 x cc

输出结果完全和输入的mmnn无关。


Atrous Convolution(空洞卷积)

空洞卷积其实就是卷积计算时“镂空”,类似这样子:

上图是采样率(dilation rate)为2的空洞卷积,其实也就是间隔。正常卷积其实就是采样率为1的空洞卷积。

在神经网络中,为了增加感受野且降低计算量,我们总会进行下采样,

但是这样虽然可以增加感受野,但空间分辨率降低了。

空洞卷积可以尽量不丢失分辨率的情况下仍然扩大感受野。

假设一张图片88 x 88,经过一个33 x 33的卷积,再过一个pooling,结果是33 x 33

假设一张图片88 x 88,经过一个55 x 55的空洞卷积,结果是44 x 44

而且常常空洞卷积会伴随着padding,所以输出分辨率也会保留更高。

所以空洞卷积相对于普通卷积再过池化,结果分辨率更高,扩大感受野

空洞卷积还有一个好处就是可以通过设置不同的采样率,捕获多尺度信息


但是空洞卷积也有缺点。也就是gridding(网格效应/棋盘)问题。

因为很明显空洞卷积每一个提取出来的特征,其实在原图像都是不连续的。

它会导致局部信息丢失以及获取的远距离信息缺乏相关性


有一个较好的解决方法,叫做HDC(Hybrid Dilated Convolution)。

论文链接:https://ieeexplore.ieee.org/document/8354267

其实也很好理解,原来的卷积区域相当于是一个格子往八个方向各走dilation_rate格。

现在相当于先走一格,相当于普通卷积。(dilation_rate=1)

然后对这个普通卷积的区域,也就是这九个格往八个方向各走两格。

因为有9个格子,所以就有重复,没有空缺。(dilation_rate=2)

然后对这四十九个格往八个方向各走三格。(dilation_rate=3)

这样子卷积后的结果既有连续信息,也有跨越的信息。

参考:https://zhuanlan.zhihu.com/p/50369448


ASPP(Atrous Spatial Pyramid Pooling)

Deeplab v2论文链接:https://arxiv.org/abs/1706.05587

ASPP是在SPP的基础上,用Atrous Convolution(空洞卷积),增加了感受野。

主要就是用了普通空洞卷积,没有用到HDC。

对一特征图用不同dilation_rate的空洞卷积,然后拼接到一起。

控制padding保证输出特征图维度不变,例如:

nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=6, dilation=6)

参考:https://blog.csdn.net/m0_37798080/article/details/103163397

阅读全文

read_serial_sensor

2022/2/20

读取串口传感器数据

接触了很多串口通信的传感器,基本都殊途同归,就是发送一串数据,

其中包括固定的数据头,真实数据,校验和以及固定的数据尾。

用keil写过C版本的,用arduino写过c++版本的,用树莓派写过python版本的,这里记录一下。

读取的思路就是编写一个函数,这个函数每次就读取一个字节,循环调用这个函数。

函数内部就做一些标志位的判断,判断是在等待数据头还是接受数据又或者是判断校验和。

当一组数据解析完成之后再将数据更新。


以我最近使用的空气质量传感器为例,

C版本的是之前的IMU,就不放出来了,道理和C++是一样的,用结构体实现。

商品详情是这样子写的

字节 名称 说明
B1 帧头1 固定值3Ch
B2 帧头2 固定值02h
B3 数据 eC02高字节
B4 数据 eCO2低字节
B5 数据 eCH2O高字节
B6 数据 eCH2O低字节
B7 数据 TVOC高字节
B8 数据 TVOC低字节
B9 数据 PM2.5高字节
B10 数据 PM2.5低字节
B11 数据 PM10低字节
B12 数据 PM10高字节
B13 数据 Temperature 整数部分
B14 数据 Temperature 小数部分
B15 数据 Humidity 整数部分
B16 数据 Humidity 小数部分
B17 校验和 校验和

c++代码

#include "AirQualitySensor.h"

AirQualitySensor air_quality_sensor;
void AirQualitySensor::init(HardwareSerial* serial_ptr)
{   
    serial_ptr->begin(9600);
    this->serial_ptr = serial_ptr; //传入串口指针 有需要可以很方便修改成其他串口
}

const short data_length = 14;// 数据长度共14位
const short buf_bength = 17;// buf长度17位
unsigned char data_buf[buf_bength]={0};//包括数据头、数据、校验和 共17位

void AirQualitySensor::update_data()
{
    static unsigned char flag = Waiting;//状态标志位 
    static unsigned char ReceiverFront = 0;//上一次接受的数据
    static short CurrentReceiverDataNum = 0;//目前接受了多少个数据

    unsigned char Receiver = 0;//当前接受的数据
    if(serial_ptr->available()>0)
    {
        Receiver =serial_ptr->read();
    }

    //等待
    if(flag == Waiting)
    {
        //数据头
        if((ReceiverFront == 0x3C) && (Receiver == 0x02))
        {
            flag = Started;
            data_buf[0] = ReceiverFront;
            data_buf[1] = Receiver;         
        }
        else
        {
            ReceiverFront = Receiver;
        }
    }
    //获取数据
    else
    {
        //将数据放入buf 包括校验和
        if(CurrentReceiverDataNum < data_length +1)
        {
            data_buf[CurrentReceiverDataNum+2] = Receiver;
            CurrentReceiverDataNum ++;
        }
        //数据读取完毕 检验校验和 通过则对buf解析放入类的属性
        else
        {
            unsigned char CheckSum = 0;
            for(int i=0;i<2+data_length;i++)
            {
                CheckSum += data_buf[i];
            }

            if ((CheckSum&0xff) == data_buf[buf_bength-1])
            {
                air_quality_sensor.CO2 = (data_buf[2]<<8) + data_buf[3];
                air_quality_sensor.CH2O = (data_buf[4]<<8) + data_buf[5];
                air_quality_sensor.TVOC = (data_buf[6]<<8) + data_buf[7];
                air_quality_sensor.PM2_5 = (data_buf[8]<<8) + data_buf[9];
                air_quality_sensor.PM10 = (data_buf[10]<<8) + data_buf[11];
                if(((data_buf[12] & 0x0040)>>6)==0)
                {
                    air_quality_sensor.temperature = CombineIntegerDecimal(data_buf[12],data_buf[13]);
                }
                else
                {
                    air_quality_sensor.temperature = -CombineIntegerDecimal(data_buf[12],data_buf[13]);
                }

                
                air_quality_sensor.humidity = CombineIntegerDecimal(data_buf[14],data_buf[15]);             
            }
            else
            {
                Serial.println("CheckSum error");
            }

            //不论校验和通不通过都重置参数
            ReceiverFront =0;
            CurrentReceiverDataNum =0;
            flag = Waiting;

        }
    }   
}

//合并整数和小数
float CombineIntegerDecimal(unsigned char integer,unsigned char decimal)
{
	// 将整数和小数拼接成字符串再转回数字
	String combine = String(integer) +"."+String(decimal);
	float result = combine.toFloat();

    return result;
}
#pragma once
#include <Arduino.h>
class  AirQualitySensor
{
    private:
        HardwareSerial* serial_ptr;
 
    public:
        int CO2;//二氧化碳
        int CH2O;//甲醛
        int TVOC;//总挥发性有机物
        int PM2_5;//PM2.5
        int PM10;//PM10
        float temperature;//温度
        float humidity;//湿度

        void init(HardwareSerial* serial_ptr);
        void update_data();

};

float CombineIntegerDecimal(unsigned char integer,unsigned char decimal);
enum GetDataFlag
{
    Waiting, //等待
    Started //开始
};

python代码

# coding=utf-8
from enum import Enum

class Flag(Enum):
    Waiting = 0
    Started = 1

def bytes2int(byte):
    return int.from_bytes(byte,"little",signed=False) #python读串口最麻烦的就是编码问题 注意这里要用python3

class AirQualitySensor:

    def __init__(self, serial):
        self._serial = serial
        
        self.data_length = 14  # 有效数据长度
        self.buf_length = 17  # buf长度
        self.data_buf = [0] * self.buf_length  # 包括帧头、帧尾和校验和

        self.receiver = 0  # 当前接受到的数据
        self.receiver_front = 0  # 上一帧接受到的数据
        self.now_data_num = 0  # 目前接受了多少个数据
        self.flag = Flag.Waiting  # 当前flag 用于判断帧头是否满足 进而判断是否接受数据

        # 数据
        self.CO2 = 0
        self.CH2O = 0
        self.TVOC = 0
        self.PM2_5 = 0
        self.PM10 = 0
        self.temperature = 0
        self.humidity = 0

    def update_data(self):      
        self.receiver = bytes2int(self._serial.read(1)) # 接受数据

        # 等待数据头校验通过
        if self.flag == Flag.Waiting:
            if self.receiver_front == bytes2int(b'\x3C') and \
               self.receiver == bytes2int(b'\x02'):             
                self.data_buf[0] = self.receiver_front
                self.data_buf[1] = self.receiver
                
                self.flag = Flag.Started
            else:
                self.receiver_front = self.receiver

        # 获取数据
        else:
            # 将数据放入buf 包括最后一位校验和
            if self.now_data_num + 2 < self.buf_length:
                self.data_buf[self.now_data_num + 2] = self.receiver
                self.now_data_num += 1
                
            # 数据读取完毕 校验校验和
            else:
                check_sum = sum(self.data_buf[:-1])                              
                if (check_sum & 0x00ff) == self.data_buf[-1]:
                    # 将数据放入成员变量
                    self.CO2 = (self.data_buf[2] << 8) + self.data_buf[3]
                    self.CH2O = (self.data_buf[4] << 8) + self.data_buf[5]
                    self.TVOC = (self.data_buf[6] << 8) + self.data_buf[7]
                    self.PM2_5 = (self.data_buf[8] << 8) + self.data_buf[9]
                    self.PM10 = (self.data_buf[10] << 8) + self.data_buf[11]
                    self.humidity = float(str(self.data_buf[14]) + "." + str(self.data_buf[15]))
                    # 温度需要判断正负
                    if ((self.data_buf[12] & 0x0040) >> 6) == 0:
                        self.temperature = float(str(self.data_buf[12]) + "." + str(self.data_buf[13]))
                    else:
                        self.temperature = -float(str(self.data_buf[12]) + "." + str(self.data_buf[13]))
                else:
                    print("check sum error!")

                # 不论是否通过检验 都重置
                self.receiver = 0
                self.now_data_num = 0
                self.flag = Flag.Waiting

阅读全文

raspberry_use_serial0

2022/2/20

树莓派使用serial0配置

要用树莓派的serial0要先禁用蓝牙,不然映射关系不对

树莓派运行ser.inWaiting()会报错inappropriate ioctl for device


正常没有配置过的树莓派是serial1 -> ttyAMA0

而serial0才是GPIO中的UART。

可以通过以下命令查看映射关系

sudo ls -l

编辑cmdline.txt

sudo cp /boot/cmdline.txt /boot/cmdline.txt.bak # 备份文件
sudo vi /boot/cmdline.txt # 编辑文件

将其中console=serial0,115200 console=tty1删掉


关闭蓝牙

sudo systemctl disable hciuart

编辑config.txt

sudo vi /boot/config.txt

在最下面添加dtoverlay=pi3-disable-bt


重启树莓派

sudo reboot

重新查看映射关系 可以发现serial0 -> ttyAMA0



参考:https://blog.csdn.net/RambleMY/article/details/81206090

阅读全文

cnn_note

2022/2/20

CNN notes

接触了很多图像处理,也接触了卷积,也写过Bp神经网络,感觉学得还是比较杂。

打算系统学习一下卷积神经网络(Convolutional Neural Network,CNN)。

学习网址是B站:https://www.bilibili.com/video/BV1e54y1b7uk

顺便想试一下用英文写博客。So,Let’s get started!


Chapter 1

Section 1

        ~~~~~~~~In section 1,it introduces the convolution from the full connection.


Section 2

        ~~~~~~~~In section 2, it introduces the convolution kernel of the example of vertical edge detection, which is also called a filter. Then it introduces how convolution is calculated. The example of vertical edge detection is easy to understand, and the visualization is also vivid.

        ~~~~~~~~For the example of vertical edge detection filter, it is as follows

        ~~~~~~~~When the nearby pixels are not much different, due to the 1 and -1 on the left and right of the convolution kernel, when the kernel passes the image, the final calculation result will tend to 0. But when there is a vertical edge, the gradient changes a lot from left to right. If it changes from light to dark, the result of light pixel multiplies the 1 add the result of dark pixel multiplies the -1 will be huge. On the contrary, if it changes from dark to light, the result will be small. So when the kernel passes the whole image, the edge features are extracted.


Section 3

        ~~~~~~~~In section 3, it introduces the horizontal edge detection kernel and other filters, such as the Sobel filter, Scharr filter. And it pointed out that the kernel can extract other features if the parameters of the kernel can be learned. And it can extract the features better and it will be more suitable for our needs.


Section 4

        ~~~~~~~~In section 4,it introduces the padding. It talked about the disadvantages of convolution at the beginning.

1. The convolution will decrease the size of the image. If the original image’s size is n×nn \times n, and the step is 11, the result’s size will be (nf+1)×(nf+1)(n-f+1) \times (n-f+1) after the $ f \times f $ convolution

2. The convolution will only calculate the edge of the image once, the result of extraction will be poor. So after the convolution, the image edge information is lost.

        ~~~~~~~~If we want to solve the problem above, we can use padding, which is adding the 00 around the image.If the number of padding is pp,then the original n×nn \times n image will be (n+2p)×(n+2p)(n+2p) \times (n+2p).If the result size is equal to the original image after convolution, the convolution is said “same convolution”.If we use “same convolution”,we can get (n+2pf+1)=n(n+2p-f+1)=n .So p=f12p=\frac{f-1}{2}.And this is why the filter’s size is always an odd number. If the size is an even number, it can’t be padding symmetrically.


Section 5

        ~~~~~~~~In section 5,it introduces the “strided convolution”,whose step is not 1.

        ~~~~~~~~According to the section above,we can summarize that if the image’s size if n×nn \times n,and the padding number and step is pp and ss,then the final image’s size will be n+2pfs+1×n+2pfs+1\lfloor \frac{n+2p-f}{s}+1 \rfloor \times \lfloor \frac{n+2p-f}{s}+1 \rfloor after the f×ff \times f convolution.

        ~~~~~~~~At the end of the video, it tells that the definition of “convolution” in the math book is symmetric about the negative diagonal and then multiply correspondingly and get the sum. But in the deep-learning, we cancel the process of symmetry. And we call cross-multiplication convolution.


Section 6

        ~~~~~~~~In section 6, it introduces how the convolution works if it has multi-channels.

        ~~~~~~~~If the original n×nn \times n image,and the step is 1,no padding,the size of result will be (nf+1)×(nf+1)×k(n-f+1) \times (n-f+1) \times k after the convolution of kk kernels whose size is f×f×cf \times f \times c.

Section 7

        ~~~~~~~~In section 7, it connects the convolution neural network with the non-convolution forward network. If we understand convolution as wxw * x, then the kernel can be understood as ww and the image can be understood as xx. So we can add bias and non-linear functions like non-convolution forward network and get the output finally. Noting that a convolution kernel corresponds to a bias, so if the kernel size is 3×3×33 \times 3 \times 3, it will have 27+127+1 parameters. And we can find that the number of parameters is nothing to do with the size of the input image. It is better to avoid overfitting.


Section 8

        ~~~~~~~~In section 8, it gives an example of the convolution neural network. The network constitutes multi-layer convolution, pooling and fully connected, flattening, and logistic regression. Generally, as the network deepens, the height and width will decrease and the channels will increase.

        ~~~~~~~~If you want to learn more about flattening and logistic regression such as softmax, you can refer to the website below.

https://zhuanlan.zhihu.com/p/105722023


Section 9

        ~~~~~~~~In section 9, it introduces pooling. The pooling can also be understood as a filter, which also has size and step. An example of “max pooling”, which is used most commonly, is taking the max value of the area where the filter passes by. And the “average pooling” is taking the average. Pooling is independent of one channel, and the image will keep the original number of channels after pooling. The role of pooling that I understand before is to reduce the calculation. And I think it will lose some information. But after watching the video, I have some other understanding. If one part doesn’t include features, the max value of the part will also be small, so we get the max value will not lose the information that we need. And if one part includes features, the max value is enough to express the feature.


Section 10

        ~~~~~~~~In section 10, it gives an example of a network. The network constitutes the convolution layer, pooling layer, and fully connected layer. The layer number is the number of a layer that have parameters generally. The convolution layer and the pooling layer are known as one layer because the pooling layer has no parameters. A Fully connected layer is like the hidden layer to the output layer in a non-convolution network.


Section 11

        ~~~~~~~~In section 11, it shows the advantages of convolution compared with fully connected. One is parameter sharing and the second is the sparsity of connections. The number of fully connected layer parameters is large, and it will increase with the increased size of the input image. Relatively, the number of convolution parameters is small, and it will not increase with the increased size of the input image. The convolution is parameter sharing, so the parameter will play a role in the whole image. If the convolution filter can extract a feature, then the same feature in different locations will also be extracted. The convolution builds the sparse connection with fewer parameters. In short, the result of convolution depends on one part of the image. And the result reflects the connection between this part. This kind of connection is important because the same feature in the different images is not exactly the same. In other words, the feature you extract in the new image may be similar to the feature you extract when you trained, maybe there are only a bit pixels changes. If there are some similar features, it will be thought of as the same feature after convolution.


Section 12

        ~~~~~~~~It is an interview about Yann in section 12.


        ~~~~~~~~OK, this is all of the learning notes for the first chapter. After learning the first chapter, I have a clearer understanding of how a network works. Next, it is more important to learn how to design hyperparameters to make the network work well.



第二节

果然是试试就逝世,感觉还是中文通俗易写一点哈哈

2.1

概述接下来要讲一些常用的网络


2.2

讲一些经典的网络结构,LeNet-5、AlexNet、VGG-16。

试了一下画网络结构。

用的是NN-SVG,http://alexlenail.me/NN-SVG

感觉就是没法标注出filter的大小,定制化程度不是很高,感觉视频中画的就很详细、通俗易懂。

效果如下:

LeNet-5:

AlexNet:(省去了池化)

VGG-16:(用流程图画的,same卷积)

另外一些拓展内容:

1.阅读以往文献可能会发现用sigmoid和tanh比较多,而没有用ReLU。

2.AlexNet原文用到了局部响应归一,也就是把同一像素不同通道归一,但是后续证明这不太管用。


2.3

网络太深训练会很难,视频里讲到因为会出现梯度消失和爆炸等问题。

看了这篇文章,我对这个问题有了另一个理解。链接:https://www.zhihu.com/question/64494691

当输入模值恒大于1,多层回传,梯度呈现倍数增长,而这一过程不断重复,就会一直增长到nan。这就是梯度爆炸。当输入模值恒小于1,多层回传,梯度呈现倍数减少,而这一过程不断重复,就会一直减少到0。这就是梯度消失。

但是现在大部分框架,都有采用Bacth Normalization(简称BN),而BN的作用本质上也是控制每层输入的模值

我的理解是网络太深训练会很难,因为模型会退化。

当你层数增加到一定程度时,训练误差在下降一定之后又会反升。

ResNet是使用了残差结构的网络,使用了跳跃连接。

在网络较深的情况下,训练误差也可以保持下降。

残差块结构如下:


2.4

首先说明ResNet至少不会导致网络输出质量下降,

由于有跳跃连接,残差块偏向于学习恒等函数,output趋向于等于input。

所以我们加入残差块,不会影响网络的能力。

而残差块一方面解决了网络深度的问题,保证梯度下降。

而且如果参数学习得好的话,一定程度上可以提升网络的表现。

残差块因为有跳跃连接,要保证相加,就要维度相等,所以一般都用same convolution。

如果维度不相等,就要类似乘以一个wSw_S项,将它的维度转到相等。


2.5

1 * 1卷积其实可以理解成全连接,图像大小不变,而且可以通过滤波器的个数确定输出的通道(比如降通道)。


2.6

Inception块是指对一个图像进行多种操作后拼接起来。

比如先1 x 1的卷积,然后3 x 3的same卷积,或者same padding的max pool。

三者输出的图像都是一样的,然后将三者拼到一起,组成一个输出,大致如下。

另外讲到合理设计1 x 1的卷积作为瓶颈层,降低计算量。

大致原理其实就是在两个较大维度的层之间引进一个较小维度的瓶颈层,

将计算量变成两个较小的数相加,而不是直接两个较为维度直接做运算的相乘。


2.7

Inception网络又叫做GoogLeNet,其实就是由Inception块组成。

Inception网络的做法是引出旁枝,也就是在隐藏层就接出全连接然后softmax进行预测。

为了来说明即使在网络的开始或者中间,他们的预测都不算太差,也一定程度上避免了网络的过度学习。


2.8

深度可分离卷积(Depthwise Separable Convolution)可以很大程度的降低计算量。

原理其实在于原本假设一个nn x nn x ncn_c的图像,你要卷积就需要一个ff x ff x ncn_c的卷积核。

然后假设有kk个卷积核,最后的图像也就是 (nf+1)(n-f+1) x (nf+1)(n-f+1) x kk

那么计算量就是ncn_c x (nf+1)(n-f+1) x (nf+1)(n-f+1) x kk x ff x ff


深度可分离卷积分为两步走,第一步是Depthwise(深度卷积),第二步是Pointwise(点卷积)。

Depthwise这一步其实原理上和普通卷积是一样的,但是没有把深度合并,

所以结果图像是 (nf+1)(n-f+1) x (nf+1)(n-f+1) x ncn_c

计算量就是ff x ff x ncn_c x (nf+1)(n-f+1) x (nf+1)(n-f+1)

Pointwise这一步就是用k个1 x 1 x ncn_c的卷积核对Depthwise的结果进行卷积,

得到最终图像就是 (nf+1)(n-f+1) x (nf+1)(n-f+1) x kk

计算量是1 x 1 x ncn_c x (nf+1)(n-f+1) x (nf+1)(n-f+1) x kk

总的计算量就是ncn_c x (nf+1)(n-f+1) x (nf+1)(n-f+1) x (ff+k)(f*f+k)


所以可见节省的计算量是ff+kffk=1k+1ff\frac{f*f+k}{f*f*k}=\frac{1}{k}+\frac{1}{f*f},一般ff如果是3,kk是上百的数,

那么节约的计算量就有约110\frac{1}{10}

究其原因就是普通卷积其实将同一位置不同深度的计算量求和到了一个维度,

这一步其实就是计算量大的关键,而深度可分离卷积很好地避免了这一步,并在后续将其合并到了一起。


2.9

MobileNet_v1:

MobileNet_v2:


2.10

简单讲了EfficientNet,之前我也有了解过。

简单讲就是寻求输入图像分辨率rr,网络深度dd,网络宽度ww的最佳组合。

深度越深可以捕获更复杂的特征、但是太深也面临难训练的问题,而且网络到达一定深度增益会减少。

宽度越宽可以捕获更细的特征,但是如果宽深比差太多,也没法很好捕获到特征。

分辨率越高可以捕获更细的特征,但是带来的就是计算量的增加。

EfficientNet采用复合缩放,寻求满足计算资源的最佳rrddww

详细学习参照:https://zhuanlan.zhihu.com/p/67508423


2.11

教你怎么用github下载开源代码


2.12

迁移学习,使用预训练权重。

如果训练数据很少的话,可以直接冻结网络,然后将最后的Softmax层改成自己所需的,训练。

如果训练数据较多的话,可以直接冻结网络部分层,然后将最后的Softmax层改成自己所需的,训练。

如果训练数据很多的话,可以不要冻结,然后将最后的Softmax层改成自己所需的,训练。


2.13

由于数据太少,我们需要数据增强,在原有的数据上增加一些数据。

常用方法有

1.垂直镜像(Mirroring)

2.随机裁剪(Random Cropping)

3.色彩变化(Color Shifting)(增强网络在色彩变化的健壮性) PCA 主成成分分析

4.旋转(Rotation)、剪切(Shearing)、局部变形(Local warping)(不常用)


2.14

大致讲了一下数据所需大小以及网络人工设计以及手动处理程度。

讲了如何提高网络作用在基准数据的效果(会降低效率,难以成为产品)

1.集成,可以多个网络输出取平均值。

2.多重剪切 10-crop(原图像中心左上右上左下右下 以及垂直镜像对应)



第三节

3.1

这节讲的是目标检测,以下有三个概念,是层层递进的。

1.图像分类(classification)

2.图像分类并定位(classification of localization)

3.目标检测(object detection)


首先是图像分类并定位实现。

其实就是在我们分类的基础上,要多输出一个box来定位。

box的位置由四个参数确定,bx,by,bw,bhb_x,b_y,b_w,b_h

bx,byb_x,b_y分别代表box中心点的x,yx,y,将图像左上角定为(0,0),右下角定为(1,1),x,yx,y是分数。

以及box的宽和高(也有可能是分数,表示是某一线段的倍数)。

具体实现就是在网络的输出多加入四个参数,打label的时候也要对应打上四个参数。

一种可能的输出是 y=[pc,bx,by,bw,bh,c1,c2,...]Ty =[p_c,b_x,b_y,b_w,b_h,c_1,c_2,...]^T

pcp_c代表是否有检测对象,bx,by,bw,bhb_x,b_y,b_w,b_h代表box四个参数,c1,c2,...c_1,c_2,...代表分到哪个类(class)。

pc=1p_c=1时,损失函数要计算整个向量,当pc=0p_c=0时,损失函数只需要计算pcp_c


3.2

这节课讲的是特征检测,输出坐标,比如人脸关键点识别,关节点识别等。


3.3

这节课讲的是滑动窗口检测。

假设你训练了一个模型,输入是一个类别(比如车),

图像大小接近这个类别的最小外接正方形,然后输出预测结果。

这样子就可以用一个滑动窗口,以一定的步长遍历图像,

每次都把这个窗口调整到网络输入一样的大小裁剪图像,输出预测结果。

然后不断放大滑动窗口的大小,做同样的操作。

但是这个方法有个很大的缺点,就是计算量太大了!

步长大检测效果又不好,步长小计算成本又太大。


3.4

用卷积实现滑动窗口检测,这个想法挺奇妙的。

假设原来的检测网络是这样子的。(FC 5 x 5代表以5x5的卷积代替全连接)

如果我们需要处理16 x 16 x 3的图片,则需要4个滑动窗口裁剪原图像4次,

然后输入网络,对应输出4个1 x 1 x 4。


用卷积实现的话是这样子的。

直接将大图片进行和网络一样的操作,你会发现其实最后的2 x 2 x 4的输出就是滑动窗口的4个输出的拼接。

共享了大量的计算,减少了计算量。

(CAD画图好方便)


3.5

用卷积实现滑动窗口,使得算法高效了许多。

但是还是有个很大的问题,就是检测到的窗口不准确。

而且很多时候我们检测的物体并不是正方形的。

所以提出了YOLO算法,以下是个人理解:

1.假设原始图像是 nn x nn x 3,我们将其分成 kk x kk 个图像。

2.这 kk x kk 个图像都对应一个输出 y=[pc,bx,by,bw,bh,c1,c2,...]Ty =[p_c,b_x,b_y,b_w,b_h,c_1,c_2,...]^T,假设长度为ll

3.那么网络输入就是nn x nn x 3,输出就是 kk x kk x ll

(yolo v1的y的长度 ll 是30,包含两个box以及20个类别,其中一个box有五个参数)

补充:

YOLO算法是以目标物的中心点来确定目标物所在方格,bx,by,bw,bhb_x,b_y,b_w,b_h定义如下。

其中绿色框代表检测的物体。

bx=0.28,by=0.13b_x =0.28,b_y=0.13,代表相对于方格的位置。

bw=1.28,bh=0.73b_w=1.28,b_h=0.73,代表框的长宽占方格边长的占比。


3.6

这节课讲交并比(IOU),用来衡量目标检测算法的优劣。

也就是你的输出框和真实框的交集比上并集,越接近1效果越好,越接近0效果越差。


3.7

非极大值抑制(Non-max suppression)

NMS是为了来解决很多box都指向了同一个物体,我们从中挑选出最大的,并抑制其他的。

具体操作就是先找出这个类别中pcp_c最大的,然后抑制跟这个框IOU大的其他框,然后重复这个过程。

在用NMS之前,会直接先筛除掉一个阈值以下的pcp_c框,比如筛除pc0.6p_c \leqslant 0.6的框,降低计算量。

我原本有个想法就是直接对每个类别,取pcp_c最大的box不就行了,排个序,都不用用到NMS吧。

但是后来想了想,如果一个图像中出现两个同类别的物体,那便找不出对应这两个物体的框了。

NMS即能保证不会多个框指向同个物体,也保证同一类别的多个物体可以被框选。


3.8

以上算法一个格子只能检测一个物体,虽然当格子分得足够多时,出现这种情况的概率就很小。

使用anchor_boxes就可以实现一个格子检测多个物体。

假设输出是y=[pc1,bx1,by1,bw1,bh1,c11,c21,...,pc2,bx2,by2,bw2,bh2,c12,c22,...]Ty =[p_{c1},b_{x1},b_{y1},b_{w1},b_{h1},c_{11},c_{21},...,p_{c2},b_{x2},b_{y2},b_{w2},b_{h2},c_{12},c_{22},...]^T

前后分别对应两个anchor_boxes对应的形状的输出。

具体做法是先定义anchor_boxes,

比如第一个定义扁平的框,用来检测车,第二个定义瘦高的框,用来检测人。

当一个格子内出现物体时,就看这个框的形状和anchor_boxes哪个像。

比如如果是扁平的,那么pc1p_{c1}就是1,然后后续参数对应,瘦高同理。

但是其实很多时候同类别的,他的anchor_boxes也相似,

假设是都是第一个类别,

很少可能出现y=[pc1,bx1,by1,bw1,bh1,1,c21,...,pc2,bx2,by2,bw2,bh2,1,c22,...]Ty =[p_{c1},b_{x1},b_{y1},b_{w1},b_{h1},1,c_{21},...,p_{c2},b_{x2},b_{y2},b_{w2},b_{h2},1,c_{22},...]^T的情况,

而且很少出现同类别的两个物体的中心点在同一个格子内。

所以有个疑问,是不是可以直接y=[pc1,bx1,by1,bw1,bh1,pc2,bx2,by2,bw2,bh2,c1,c2,...]Ty =[p_{c1},b_{x1},b_{y1},b_{w1},b_{h1},p_{c2},b_{x2},b_{y2},b_{w2},b_{h2},c_1,c_2,...]^T

好像yolo v1中就是这样子的。


3.9

总结一下yolo的过程。

training:输入图像,将图像分割,给每个格子打标签,对应最终的输出。

predict:输入图像,输出每个格子对应的结果,剔除pcp_c值小的,对每个类别进行NMS。


3.10

RCNN:伴随区域的CNN,也就是用其他算法尝试选取有意义的区域来运行目标检测算法。

随之后来速度不断提升的fast RCNN、以及faster RCNN。


3.11

这节课讲的是语义分割,其实就是对所有像素分类。

还有另一个术语是实例分割,他们两者的区别是:

语义分割只对类别分割,实例分割是分完类别之后还要对实例分割。

比如语义分割只需要区分人和背景,实例分割就要做到区分人和背景的同时,将不同的人也分割出来。


3.12

这节课讲的是反卷积(transpose convolution)。

No=(Ni1)s+k2pN_o =(N_i-1)*s+k-2p

之前看过unet的上采样,看一个教程说是插值,这节课看完才知道原来是反卷积。

具体实现如下:


3.13 && 3.14

这两节课讲UNet。图片参照论文。UNet论文:https://arxiv.org/abs/1505.04597

网络结构比较简单。

就是不断卷积池化下采样,可以理解成编码过程。

然后不断卷积反卷积上采样,可以理解成解码过程。

然后同维度的层数之间concat拼接,保证了网络既有浅层特征又有深层特征。

可以理解成深层的网络可以大概把物体分割出来,然后浅层的网络把边缘细节给分割出来。

这样子两者一结合,就可以较好地实现语义分割。

UNet学习链接:https://www.bilibili.com/video/BV1Dy4y1x7zt


顺便记录一下UNet++,UNet++论文:https://ieeexplore.ieee.org/document/9380415

UNet++就是在UNet基础上,将较近的层之间做了拼接,并且引出多个loss。

阅读全文

change_python_version_in_linux_shell

2021/12/8

在linux shell改变python版本

如果我们只是普通编写python代码,我们完全可以用anaconda管理自己IDE的python环境

例如我就是用anaconda管理pycharm里的运行环境,很方便也很有用。

那么你可能会问,为什么要改变shell中的python版本,基本很少人用shell写python脚本。

答案就是你需要编译源码

许多源代码都有python文件,编译它们需要python2。所以我们需要知道如何在shell中管理python版本。


在 Ubuntu 中,我们可以使用 update-alternatives,它可以设置不同版本的优先级。

如果我们需要使用 python2.,我们可以将python2的优先级设置为高于python3。 配置如下


1.检查链接

ls -l /usr/bin/python

结果应该是 /usr/bin/python -> /etc/alternatives/python


2.添加python2和python3配置

(注意! python2.x 和 python3.x 需要更换成你的python2 and python3 版本)

sudo update-alternatives --install /usr/bin/python python /usr/bin/python2.x 2 
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.x 1

3.验证

检查shell中的python版本看看更改是否生效

python --version

(如果安装了anaconda,需要在 ~/.bashrc注释掉anaconda的路径然后重启shell)

阅读全文

Deepface_config

2021/9/23

最近有想要看一下人脸表情分析,这方面已经很成熟了。找到了Deepface,记录一下配置过程。

安装Deepface

安装deepface,它会顺带把opencv也安装好。

pip install deepface

安装到最后报错

ERROR: Cannot uninstall ‘wrapt’. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall

解决方法:

pip install wrapt --ignore-installed

运行demo

from deepface import DeepFace
import cv2 as cv

img = cv.imread("happy.jpg")
prediction = DeepFace.analyze(img)
print(prediction)

应该就会下载权重文件,如果下载得慢的话可以手动下载然后放进.deepface文件夹里。下载过程会显示下载的链接。


如果期间报错

OSError: Unable to open file (truncated file: eof = 207375855, sblock->base_addr = 0, stored_eof = 538771776)

说明是下载错误然后没有删掉下载错误的文件,所以文件损坏打不开,删掉对应文件就好了。

阅读全文

DialogueRobot

2021/9/23

对话机器人

最近原本想整一个对话机器人的,但是后续没有时间,记录一下踩过的一些坑和学到的一些知识吧。

安装ChatterBot

github链接:https://github.com/gunthercox/ChatterBot

1.创建anaconda环境,python3.8以下

我是直接用3.7,原因是源码中用了time.clock。

而从python3.8开始,time模块下不支持clock了。

所以如果用3.8以上运行代码就会报错。

2.先手动安装scapy

不要直接pip install chatterbot,因为里面有个难缠的scapy。

如果你直接安装他也是卡在安装scapy那里,然后满屏报错,不断降版本。

所以干脆我们先把低版本安装了,使用

conda install spacy==2.0.12

3.安装ChatterBot

pip install chatterbot

运行程序,解决报错

库安装好了,README有运行demo,如下。

from chatterbot import ChatBot
from chatterbot.trainers import ChatterBotCorpusTrainer

chatbot = ChatBot('Ron Obvious')

# Create a new trainer for the chatbot
trainer = ChatterBotCorpusTrainer(chatbot)

# Train the chatbot based on the english corpus
trainer.train("chatterbot.corpus.english")

# Get a response to an input statement
chatbot.get_response("Hello, how are you today?")

错误1

OSError: [E050] Can’t find model ‘en’. It doesn’t seem to be a shortcut link, a Python package or a valid path to a data directory.

报错原因:没有en

解决办法:

python -m spacy download en 

Tips

1.强调!用管理员权限打开anaconda,不然下好了也会出现Error: Couldn’t link model to 'en’的情况。

2.会报连接错误的,多试几次多换网试一下。

3.试过将文件下载到本地然后pip下载,会报错,不知道为什么不行。链接如下

https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-2.0.0/en_core_web_sm-2.0.0.tar.gz

错误2

chatterbot.exceptions.OptionalDependencyImportError: Unable to import “yaml”.
Please install “pyyaml” to enable chatterbot corpus functionality:
pip3 install pyyaml

报错原因:没有pyyaml

解决办法:

pip3 install pyyaml

错误3

FileNotFoundError: [Errno 2] No such file or directory: ‘你的路径\chatterbot_corpus\data\english’

报错原因:没有训练的数据。需要去下载,

解决方法:手动去链接下载,然后放到你的路径或者代码运行路径

链接:https://github.com/gunthercox/chatterbot-corpus

代码运行路径可以通过以下方式获取

import os
abspath = os.getcwd()  # 获取当前路径
print(abspath)

搞定了√ 可以正常运行。

不够这个语音助手特别依赖语料库,语料库质量不好就…很人工智障。

但是我也没有找到特别好的语料库,点进去质量都不是很好,很多答非所问。


原理参考:https://blog.csdn.net/langsiming/article/details/103838855

改进参考:https://zhuanlan.zhihu.com/p/34927757

语料参考:https://github.com/candlewill/Dialog_Corpushttps://github.com/codemayq/chinese_chatbot_corpus

关于多种语音助手可以参考:https://blog.csdn.net/tian_panda/article/details/80664578


关于语音助手好像都是比较老的文章了,看到的都是2017附近的文章。

找到了一个较新的,2021年5月份还有更新的。

看了一下README,好像效果也不错的,未来有需要的话可以试一试。

链接:https://github.com/yangjianxin1/GPT2-chitchat

阅读全文